<template>
  <ElButtonGroup>
    <el-button title="导入流程图" @click="refFile.click()">导入流程图</el-button>
    <el-tooltip effect="light" placement="bottom">
      <template #content>
        <div style="color: #409eff">
          <el-button link title="下载为XML文件" @click="downloadProcessAsXml()">
            下载为XML
          </el-button>
          <br/>
          <el-button link title="下载为SVG文件" @click="downloadProcessAsSvg()">
            下载为SVG
          </el-button>
          <br/>
          <el-button link title="下载为BPMN文件" @click="downloadProcessAsBpmn()">
            下载为BPMN
          </el-button>
        </div>
      </template>
      <el-button title="下载流程" preIcon="ep:download">
        下载流程
      </el-button>
    </el-tooltip>
    <el-tooltip effect="light">
      <el-button title="预览XML" @click="previewProcessXML()">
        预览XML
      </el-button>
    </el-tooltip>
  </ElButtonGroup>
  <el-divider direction="vertical"/>
  <el-button type="primary" title="流程图保存" @click="saveBpmn">流程保存</el-button>
  <el-popconfirm
      title="确认要清空流程图？"
      width="200"
      @confirm="clearBpmn"
  >
    <template #reference>
      <el-button type="danger" title="流程图清空">流程清空</el-button>
    </template>
  </el-popconfirm>
  <el-divider direction="vertical"/>
  <ElButtonGroup>
    <el-button @click="handleZoom(0.2)">+ 放大</el-button>
    <el-button @click="handleZoom(-0.2)">- 缩小</el-button>
  </ElButtonGroup>
  <!-- 用于打开本地文件-->
  <input
      type="file"
      id="files"
      ref="refFile"
      style="display: none"
      accept=".xml, .bpmn"
      @change="importLocalFile"
  />

  <div class="processDrawBody">
    <div class="containerBox">
      <div id="canvas" ref="canvas" :style="{height: canvasHeight}"></div>
      <!-- 流程属性器，负责编辑每个流程节点的属性 -->
      <CustomProcessPenal
          key="penal"
          :bpmnModeler="modeler"
          :engine="engine"
          class="process-panel"
          v-draggable
      />
    </div>
  </div>

  <el-dialog v-model="previewModelVisible" title="流程图" width="80%">
    <!-- append-to-body -->
    <div>
      <codemirror
          v-model="previewResult"
          placeholder="Code gose here..."
          :lineNumbers="true"
          :lineWrapping="true"
          :autoFormatOnLoad="true"
      />
    </div>
  </el-dialog>

</template>

<script setup lang="ts">
import {ref, onMounted, onUnmounted, computed} from 'vue';
import BpmnModeler from 'bpmn-js/lib/Modeler';
// 标签解析 Moddle
import activitiModdleDescriptor from '@/components/bpmn/moddle-extensions/activiti.json'
import camundaModdleDescriptor from '@/components/bpmn/moddle-extensions/camunda.json'
import flowableModdleDescriptor from '@/components/bpmn/moddle-extensions/flowable.json'
// 标签解析 Extension
import activitiModdleExtension from '@/components/bpmn/moddle-extensions/activiti'
import camundaModdleExtension from '@/components/bpmn/moddle-extensions/camunda'
import flowableModdleExtension from '@/components/bpmn/moddle-extensions/flowable'

import CustomPalette from "@/components/bpmn/palette";
import CustomContentPadProvider from '@/components/bpmn/content-pad'
import CustomProcessPenal from '@/components/bpmn/penal'

import customTranslate from "@/components/bpmn/translate/CustomTranslate";
import DefaultEmptyXML from '@/components/bpmn/moddle-extensions/defaultEmpty'
import {Codemirror} from "vue-codemirror"
import {DateFormat} from "@/core/common/DateUtil";
import {useUserOptionStore} from "@/core/common/StorageUtil";
import {ElMessage} from "element-plus";

const CAMUNDA_ENGINE = "camunda";
const ACTIVITI_ENGINE = "activiti";
const FLOWABLE_ENGINE = "flowable";

const {engine, value, processId, processName, canvasHeight} = defineProps({
  'engine': {
    type: String,
    default: () => "camunda"
  },
  'value': {
    type: String,
    default: () => {
    }
  },
  'processId': {
    type: String,
    default: () => {
    }
  },
  'processName': {
    type: String,
    default: () => {
    }
  },
  'canvasHeight': {
    default: () => 'calc(100vh - 180px)'
  }
});

const userOptionStore = useUserOptionStore();

const scale = ref(1);

const additionalModules = computed(() => {
  const Modules: any[] = [
    {translate: ["value", customTranslate]},
    CustomContentPadProvider,
    CustomPalette,
  ]

  switch (engine) {
    case CAMUNDA_ENGINE: {
      Modules.push(camundaModdleExtension)
      break;
    }
    case ACTIVITI_ENGINE: {
      Modules.push(activitiModdleExtension)
      break;
    }
    case FLOWABLE_ENGINE: {
      Modules.push(flowableModdleExtension)
      break;
    }
  }
  return Modules
})

const moddleExtensions = computed(() => {
  const Extensions: any = {}
  switch (engine) {
    case CAMUNDA_ENGINE: {
      Extensions.camunda = camundaModdleDescriptor
      break;
    }
    case ACTIVITI_ENGINE: {
      Extensions.activiti = activitiModdleDescriptor
      break;
    }
    case FLOWABLE_ENGINE: {
      Extensions.flowable = flowableModdleDescriptor
      break;
    }
  }
  return Extensions
})

const handleKeydown = (event: KeyboardEvent) => {
  if (event.key === 's' && (navigator.platform.match("Mac") ? event.metaKey : event.ctrlKey)) {
    event.preventDefault();
    saveBpmn()
  }
};

const modeler = ref<BpmnModeler>();
const canvas = ref<HTMLElement>();
let bpmnModeler: BpmnModeler;

onMounted(() => {
  bpmnModeler = new BpmnModeler({
    container: canvas.value,
    additionalModules: additionalModules.value,
    moddleExtensions: moddleExtensions.value
  });
  modeler.value = bpmnModeler;
  modeler.value?.get<any>('keyboard').bind(document);
  const xml = value || userOptionStore.drawBpmnXml;
  createNewDiagram(xml);
  window.addEventListener('keydown', handleKeydown);
})

onUnmounted(() => {
  modeler.value?.get<any>('keyboard').unbind(document);
  window.removeEventListener('keydown', handleKeydown);
});

const saveBpmn = async () => {
  const {error, xml} = await bpmnModeler.saveXML()
  if (error) {
    console.error(`[Process Designer Warn ]: ${error.message || error}`)
  }
  if (xml === undefined) {
    return;
  }
  userOptionStore.setDrawBpmnXml(xml)
  ElMessage({
    showClose: true,
    message: '流程保存成功',
    type: 'success',
  })
}

const clearBpmn = () => {
  createNewDiagram('')
}

const handleZoom = (flag: number) => {
  // 放大缩小倍数flag可以自行设置
  if (flag < 0 && scale.value <= 0.3) {
    return;
  }
  scale.value += flag;
  bpmnModeler.get<any>("canvas").zoom(scale.value);
};

/* 创建新的流程图 */
const createNewDiagram = async (xml: string) => {
  const timestamp = Date.now()
  const newId: string = processId || `Process_${timestamp}`
  const newName: string = processName || `业务流程_${timestamp}`
  let xmlString = xml || DefaultEmptyXML(newId, newName)
  try {
    let {warnings} = await bpmnModeler.importXML(xmlString)
    console.log(warnings, 'warnings')
    if (warnings && warnings.length) {
      warnings.forEach((warn) => console.warn(warn))
    }
    bpmnModeler.get<any>('canvas', undefined).zoom('fit-viewport', 'auto');
  } catch (e: any) {
    console.error(`[Process Designer Warn]: ${e.message || e}`)
  }
}

// 下载流程图到本地
const downloadProcess = async (type: string) => {
  try {
    // 按需要类型创建文件并下载
    if (type === 'bpmn20.xml' || type === 'bpmn') {
      const {error, xml} = await bpmnModeler.saveXML()
      if (error) {
        console.error(`[Process Designer Warn ]: ${error.message || error}`)
      }
      if (xml === undefined) {
        return;
      }

      let {href, filename} = setEncoded(type, xml)
      downloadFunc(href, filename)
    } else {
      const {svg} = await bpmnModeler.saveSVG()
      let {href, filename} = setEncoded('svg', svg)
      downloadFunc(href, filename)
    }
  } catch (e: any) {
    console.error(`[Process Designer Warn ]: ${e.message || e}`)
  }

  // 文件下载方法
  function downloadFunc(href: string, filename: string) {
    if (href && filename) {
      let a = document.createElement('a')
      a.download = filename //指定下载的文件名
      a.href = href //  URL对象
      a.click() // 模拟点击
      URL.revokeObjectURL(a.href) // 释放URL 对象
    }
  }
}

// 根据所需类型进行转码并返回下载地址
const setEncoded = (type: string, data: string | number | boolean) => {
  const filename = 'process' + DateFormat(new Date(), 'yyyyMMddhhmmss');
  const encodedData = encodeURIComponent(data)
  return {
    filename: `${filename}.${type}`,
    href: `data:application/${
        type === 'svg' ? 'text/xml' : 'bpmn20-xml'
    };charset=UTF-8,${encodedData}`,
    data: data
  }
}

const refFile = ref<any>();
// 加载本地文件
const importLocalFile = () => {
  const file = refFile.value.files[0]
  const reader = new FileReader()
  reader.readAsText(file)
  reader.onload = function () {
    if (this.result) {
      let xmlStr = this.result.toString();
      if (xmlStr) {
        createNewDiagram(xmlStr)
      }
    }
  }
}

const downloadProcessAsXml = () => {
  downloadProcess('bpmn20.xml')
}
const downloadProcessAsBpmn = () => {
  downloadProcess('bpmn')
}
const downloadProcessAsSvg = () => {
  downloadProcess('svg')
}

const previewModelVisible = ref(false)
const previewResult = ref('')
const previewProcessXML = () => {
  bpmnModeler.saveXML({format: true}).then(({xml}) => {
    if (xml) {
      previewResult.value = xml || ''
      previewModelVisible.value = true
    }
  })
}

</script>

<script lang="ts">
export default {
  name: "SillyBpmn",
}
</script>

<style lang="scss">
@import 'bpmn-js/dist/assets/diagram-js.css';
// 左边工具栏以及编辑节点的样式
@import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css';
@import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css';
@import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css';

.processDrawBody {
  text-align: left;
}

.containerBox {
  height: 100%;
  position: relative;
}

.containerBox #canvas {
  border: 1px solid rgb(121, 121, 121);
}

/* 右下角logo */
.bjs-powered-by {
  display: none;
}

</style>